using System;
using RomanJendrusz.Amazon.ETL.Exceptions;
using RomanJendrusz.Amazon.ETL.Infrastructure.DataBase;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace RomanJendrusz.Amazon.ETL.Repositories
{
    public class Repository<Entity, Key> where Entity:class 
    {
        protected readonly ISession session;
        private readonly string keyName;
        protected readonly IQuerySource<Entity> source;


        public Repository(ISession session, Func<ISession, IQuerySource<Entity>> querySourceSelection, string keyName)
        {
            if (session == null) throw new ArgumentNullException("session");
            if (querySourceSelection == null) throw new ArgumentNullException("querySourceSelection");
            if (keyName == null) throw new ArgumentNullException("keyName");
            source = querySourceSelection.Invoke(session);
            this.session = session;
            this.keyName = keyName;
        }

        public Entity FindBy(Key entityKey)
        {
            return source.SingleOrDefault(GetKeyLambda(entityKey)) ;
        }

        public Entity FindBy(Func<Entity,bool> predicate)
        {
            try
            {
                return source.Where(predicate).Single();
            }
            catch (InvalidOperationException ex)
            {
                throw new SearchCriteriaException("Found more then one entity matching the criteria", ex);
            }
        }

        private Expression<Func<Entity, bool>> GetKeyLambda(Key entityKey)
        {
            var parameter = Expression.Parameter(typeof (Entity), "entity");
            var propertyExpression = Expression.Property(parameter, typeof (Entity).GetProperty(keyName));
            return Expression.Lambda<Func<Entity, bool>>(Expression.Equal(Expression.Constant(entityKey, typeof(Key)), propertyExpression), parameter);
        }

        public IList<Entity> Where(Func<Entity, bool> predicate)
        {
            return source.Where(predicate).ToList();
        }

        public IList<Entity> FindAll()
        {
            return source.ToList();
        }

        public void Add(Entity item)
        {
            source.Add(item);
        }

        public void Add(IEnumerable<Entity> items)
        {
            if (items == null) throw new ArgumentNullException("items");
            foreach (var item in items)
            {
                source.Add(item);
            }
        }

        public void Remove(Entity item)
        {
            source.Remove(item);
        }

        
    }
}